package de.chessgame.gamelogic;

import java.util.ArrayList;

import de.chessgame.gamelogic.piece.Fraction;
import de.chessgame.gamelogic.piece.King;
import de.chessgame.gamelogic.piece.Piece;
import de.chessgame.gamelogic.piece.Type;
import de.chessgame.gamelogic.piece.UndefinedPiece;
import net.minecraft.item.Item;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraftforge.common.util.Constants;

public class Board {
	private Piece[][] content;
	private Piece pieceForTurn;
	private int xOld;
	private int yOld;
	private boolean whitePlayerOnTurn;
	private ArrayList<Coordinate> highlights;
	private Piece undefined;
	private int xUndefined;
	private int yUndefined;
	private boolean whitePlayerInCheck;
	private boolean blackPlayerInCheck;
	private boolean gameIsFinished;
	private int typeOfEnding;

	public static final int DRAW = 0;
	public static final int WHITE_PLAYER_WINS = 1;
	public static final int BLACK_PLAYER_WINS = 2;

	public boolean hasUndefined() {
		return undefined != null;
	}

	public boolean isGameFinished() {
		return gameIsFinished;
	}

	public int getTypeOfEnding() {
		return typeOfEnding;
	}

	public ArrayList<Coordinate> getHighlights() {
		return highlights;
	}

	public boolean isWhitePlayerOnTurn() {
		return whitePlayerOnTurn;
	}

	public Board() {
		this.content = InitialPosition.getEmptyBoard();
		this.highlights = new ArrayList<Coordinate>();
		this.pieceForTurn = null;
		this.xOld = -1;
		this.yOld = -1;
		this.undefined = null;
		this.xUndefined = -1;
		this.yUndefined = -1;
		this.whitePlayerOnTurn = true;
		this.gameIsFinished = false;
		this.whitePlayerInCheck = false;
		this.blackPlayerInCheck = false;
	}

	private boolean currentPlayerInCheck() {
		return whitePlayerOnTurn ? whitePlayerInCheck : blackPlayerInCheck;
	}

	private void endTurn() {
		whitePlayerOnTurn = !whitePlayerOnTurn;
		if (whitePlayerOnTurn) {
			whitePlayerInCheck = isKingInCheck(content, true);
		} else {
			blackPlayerInCheck = isKingInCheck(content, false);
		}
		checkIfGameIsFinished();
	}

	private void checkIfGameIsFinished() {
		if (!areTherePossibleMoves()) {
			if (currentPlayerInCheck()) {
				finishGame(whitePlayerOnTurn ? BLACK_PLAYER_WINS
						: WHITE_PLAYER_WINS);
			} else {
				finishGame(DRAW);
			}
		}
	}

	private void finishGame(int ending) {
		gameIsFinished = true;
		typeOfEnding = ending;
	}

	public void reset() {
		this.content = InitialPosition.generate();
		this.pieceForTurn = null;
		this.xOld = -1;
		this.yOld = -1;
		this.whitePlayerOnTurn = true;
		this.undefined = null;
		this.xUndefined = -1;
		this.yUndefined = -1;
		this.gameIsFinished = false;
		this.whitePlayerInCheck = false;
		this.blackPlayerInCheck = false;
	}

	public Piece getPieceAt(int x, int y) {
		return content[y][x];
	}

	public Piece deletePieceAt(int x, int y) {
		pieceForTurn = content[y][x];
		xOld = x;
		yOld = y;
		content[y][x] = null;
		calculateHighlights();
		return pieceForTurn;

	}

	public void setSlotContent(int x, int y, Piece piece) {

		// Ignore Updates from Server, which do nothing
		Piece piece2 = content[y][x];

		if (piece == null && piece2 == null) {
			return;
		}

		if (piece != null && piece2 != null
				&& piece.getFraction() == piece2.getFraction()
				&& piece.getType() == piece2.getType()) {
			return;
		}

		if (pieceForTurn != null) {
			content[y][x] = pieceForTurn;
			if (x != xOld || y != yOld) {
				pieceForTurn.onMove(content, xOld, yOld, x, y);
				if (content[y][x].getType() == null) {
					undefined = content[y][x];
					xUndefined = x;
					yUndefined = y;
				} else {
					endTurn();
				}
			}
			pieceForTurn = null;
			xOld = -1;
			yOld = -1;
			removeHighlights();
		} else { // Server updates Clients (e.g. because of button-press)
			content[y][x] = piece;
		}
	}

	public void calculateHighlights() {

		if (pieceForTurn == null) {
			highlights.clear();
			return;
		}

		pieceForTurn.calculatePossibleMoves(content, xOld, yOld,
				currentPlayerInCheck());
		highlights = pieceForTurn.getPossibleMoves();
	}

	public boolean areTherePossibleMoves() {
		for (int y = 0; y < content.length; y++) {
			for (int x = 0; x < content[y].length; x++) {
				Piece piece = content[y][x];
				if (piece != null
						&& !(piece.getFraction() == Fraction.WHITE ^ isWhitePlayerOnTurn())
						&& piece.canMove(content, x, y, currentPlayerInCheck())) {
					return true;
				}
			}
		}
		return false;
	}

	public void removeHighlights() {
		highlights.clear();

	}

	public void resetPieceForTurn() {
		if (pieceForTurn != null) {
			content[yOld][xOld] = pieceForTurn;
			pieceForTurn = null;
			removeHighlights();
		}
		xOld = -1;
		yOld = -1;
	}

	public void setUndefinedAsPiece(Type type) {
		content[yUndefined][xUndefined] = Piece.transformPiece(undefined, type);
		undefined = null;
		xUndefined = -1;
		yUndefined = -1;
		endTurn();
	}

	public void writeToNBT(NBTTagCompound tagCompound) {
		NBTTagList tagList = new NBTTagList();

		// content:
		for (int y = 0; y < content.length; y++) {
			for (int x = 0; x < content[y].length; x++) {
				Piece piece = content[y][x];
				if (piece != null) {
					NBTTagCompound tag = new NBTTagCompound();
					tag.setByte("CellX", (byte) x);
					tag.setByte("CellY", (byte) y);
					piece.writeToNBT(tag);
					tagList.appendTag(tag);
				}
			}
		}

		if (pieceForTurn != null) {
			NBTTagCompound tag = new NBTTagCompound();
			tag.setByte("CellX", (byte) xOld);
			tag.setByte("CellY", (byte) yOld);
			pieceForTurn.writeToNBT(tag);
			tagList.appendTag(tag);
		}
		NBTTagCompound white = new NBTTagCompound();
		white.setBoolean("WhitePlayer", whitePlayerOnTurn);
		tagList.appendTag(white);

		NBTTagCompound undefinedTag = new NBTTagCompound();
		undefinedTag.setInteger("XUndefined", xUndefined);
		undefinedTag.setInteger("YUndefined", yUndefined);
		tagList.appendTag(undefinedTag);

		NBTTagCompound otherInformation = new NBTTagCompound();
		otherInformation.setBoolean("IsFinished", gameIsFinished);
		otherInformation.setInteger("TypeOfEnding", typeOfEnding);
		otherInformation.setBoolean("WhitePlayerInCheck", whitePlayerInCheck);
		otherInformation.setBoolean("BlackPlayerInCheck", blackPlayerInCheck);
		tagList.appendTag(otherInformation);

		tagCompound.setTag("Board", tagList);
	}

	public void readFromNBT(NBTTagCompound tagCompound) {
		NBTTagList tagList = tagCompound.getTagList("Board",
				Constants.NBT.TAG_COMPOUND);
		for (int i = 0; i < tagList.tagCount(); i++) {
			NBTTagCompound tag = (NBTTagCompound) tagList.getCompoundTagAt(i);
			if (tag.hasKey("CellX")) {
				byte x = tag.getByte("CellX");
				byte y = tag.getByte("CellY");
				Type type = Type.idToType(tag.getByte("Type"));
				Fraction fraction = Fraction.idToFraction(tag
						.getByte("Fraction"));
				Piece piece = Piece.createPiece(type, fraction);
				piece.readFromNBT(tag);
				content[y][x] = piece;
			} else if (tag.hasKey("WhitePlayer")) {
				whitePlayerOnTurn = tag.getBoolean("WhitePlayer");
			} else if (tag.hasKey("XUndefined")) {
				xUndefined = tag.getInteger("XUndefined");
				yUndefined = tag.getInteger("YUndefined");
			} else if (tag.hasKey("IsFinished")) {
				gameIsFinished = tag.getBoolean("IsFinished");
				typeOfEnding = tag.getInteger("TypeOfEnding");
				whitePlayerInCheck = tag.getBoolean("WhitePlayerInCheck");
				blackPlayerInCheck = tag.getBoolean("WhitePlayerInCheck");
			}
		}

		if (xUndefined >= 0 && xUndefined < 8 && yUndefined >= 0
				&& yUndefined < 8) {
			undefined = content[yUndefined][xUndefined];
		} else {
			undefined = null;
		}
	}

	private void checkIfKingsAreInCheck() {
		whitePlayerInCheck = isKingInCheck(content, true);
		blackPlayerInCheck = isKingInCheck(content, false);
	}

	/**
	 * Has to search for the king and then checks, if he is in check <i>(more
	 * inefficient)</i>
	 * 
	 * @param content
	 * @param white
	 * @return
	 */
	public static boolean isKingInCheck(Piece[][] content, boolean white) {
		Coordinate coords = searchForKing(content, white);
		if (coords != null) {
			return isKingInCheck(content, coords.x, coords.y);
		} else {
			return false;
		}
	}

	public static boolean isKingInCheck(Piece[][] content, int x, int y) {
		return ((King) content[y][x]).isInCheck(content, x, y);
	}

	/**
	 * 
	 * @param content
	 * @param white
	 *            search for white king?
	 * @return Coordinate from king
	 */
	public static Coordinate searchForKing(Piece[][] content, boolean white) {
		Fraction currentPlayerFraction = white ? Fraction.WHITE
				: Fraction.BLACK;
		for (int y = 0; y < content.length; y++) {
			for (int x = 0; x < content[y].length; x++) {
				Piece piece = content[y][x];
				if (piece != null
						&& piece.getFraction() == currentPlayerFraction
						&& piece.getType() == Type.KING) {
					return new Coordinate(x, y);
				}
			}
		}
		// on an empty board
		return null;
	}
}
